package org.hongbo.springbootebsapi.jwt;

import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.hongbo.springbootebsapi.entity.CheckResult;
import org.hongbo.springbootebsapi.entity.AuthUser;
import org.hongbo.springbootebsapi.enums.ResultCode;
import org.hongbo.springbootebsapi.service.login.ILoginService;
import org.hongbo.springbootebsapi.util.RequestContextUtil;
import org.hongbo.springbootebsapi.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = 8745128488671221651L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_USERID = "subjectid";
    private static final String CLAIM_KEY_RESPID = "respid";
    private static final String CLAIM_KEY_RESPAPPLID = "respapplid";
    private static final String CLAIM_KEY_CREATED = "created";


    @Autowired
    ILoginService loginService;

    @Autowired
    private Audience audience;

    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public String getUserIdFromToken(String token) {
        String userid;
        try {
            final Claims claims = getClaimsFromToken(token);
            userid = (String) claims.get(CLAIM_KEY_USERID);
        } catch (Exception e) {
            userid = null;
        }
        return userid;
    }

    public String getRespIdFromToken(String token) {
        String respId;
        try {
            final Claims claims = getClaimsFromToken(token);
            respId = (String) claims.get(CLAIM_KEY_RESPID);
        } catch (Exception e) {
            respId = null;
        }
        return respId;
    }

    public String getRespApplIdFromToken(String token) {
        String respAplId;
        try {
            final Claims claims = getClaimsFromToken(token);
            respAplId = (String) claims.get(CLAIM_KEY_RESPAPPLID);
        } catch (Exception e) {
            respAplId = null;
        }
        return respAplId;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(audience.getBase64Secret())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + audience.getExpiresSecond() * 1000);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    public Boolean isNeedToRefresh(String token){
        final Date createdDate = getCreatedDateFromToken(token);
        final Date refreshDate = new Date(createdDate.getTime()+audience.getRefreshSecond()*1000);
        SimpleDateFormat sdf= new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
        log.info(" isNeedToRefresh "+sdf.format( createdDate)+"  "+sdf.format(refreshDate)+" "+refreshDate.before(new Date()));
        return refreshDate.before(new Date());

    }

    public String generateToken(String userName, String userId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userName);
        claims.put(CLAIM_KEY_USERID, userId);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    public String generateToken(String userName, String userId,String respId,String respApplId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userName);
        claims.put(CLAIM_KEY_USERID, userId);
        claims.put(CLAIM_KEY_RESPID, respId);
        claims.put(CLAIM_KEY_RESPAPPLID, respApplId);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    public String generateToken(UserDetails userDetails) {
        AuthUser user = (AuthUser)userDetails;
        return generateToken(user.getUsername(), user.getUserId(), user.getRespId(), user.getRespApplId());
    }

    String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, audience.getBase64Secret())
                .compact();
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && !isTokenExpired(token);
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    public Boolean validateToken(String token) {
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        final Date expiration = getExpirationDateFromToken(token);
        return (!isTokenExpired(token));
    }


    /**
     * 验证JWT
     * @param jwtStr
     * @return
     */
    public  CheckResult validateJWT(String jwtStr) {
        CheckResult checkResult = new CheckResult();
        Claims claims = null;
        try {
            claims = parseJWT(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode(ResultCode.JWT_ERRCODE_EXPIRE.code());
            checkResult.setSuccess(false);
        } catch (SignatureException e) {
            checkResult.setErrCode(ResultCode.JWT_ERRCODE_FAIL.code());
            checkResult.setSuccess(false);
        } catch (Exception e) {
            checkResult.setErrCode(ResultCode.JWT_ERRCODE_FAIL.code());
            checkResult.setSuccess(false);
        }
        return checkResult;
    }


    /**
     *
     * 解析JWT字符串
     * @param
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String token) throws Exception {
        return Jwts.parser()
                .setSigningKey(audience.getBase64Secret())
                .parseClaimsJws(token)
                .getBody();
    }



    public Boolean validateToken(String token, UserDetails userDetails) {
        AuthUser user = (AuthUser) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
//        final Date expiration = getExpirationDateFromToken(token);
         return (
                username.equals(user.getUsername())
                        && !isTokenExpired(token)
                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()));
    }

    public String getRefreshToken(){
        String newToken=null;
        HttpServletRequest request = RequestContextUtil.getRequest();
        String authHeader = request.getHeader(audience.getAuthorization());
        if(authHeader!=null && !"".equals(authHeader)){
            final String authToken = authHeader.substring(audience.getTokenHead().length());
            log.info(this.getClass().getName()+" origin token "+ authToken );
            if(validateJWT(authToken).isSuccess()){
                if(isNeedToRefresh(authToken)){
                    newToken = audience.getTokenHead() + refreshToken(authToken);
                    log.info(" newToken "+newToken);
                }
            }
        }

        return newToken;
    }

    public boolean validRespInfo(){
        HttpServletRequest request = RequestContextUtil.getRequest();
        String authHeader = request.getHeader(audience.getAuthorization());
        final String authToken = authHeader.substring(audience.getTokenHead().length()); // The part after "Bearer "
        if(StringUtil.isEmpty(getRespIdFromToken(authToken)) || StringUtil.isEmpty(getRespApplIdFromToken(authToken))){
            log.info("jwtTokenUtil.getRespIdFromToken(authToken)==null || jwtTokenUtil.getRespApplIdFromToken(authToken)==null");
            return false;
        }
        return true;
    }

}